///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// This shader takes an RGB image and turns it into either an S-Video or Composite signal (Based on whether
//  g_compositeBlend is 0 or 1). We might also be generating a PAIR of these, using two different sets of phase inputs
//  (if g_scanlinePhases is a two-component input), for purposes of temporal aliasing reduction.


#include "cathode-retro-util-language-helpers.hlsli"
#include "cathode-retro-util-tracking-instability.hlsli"


// This is the RGB input texture. It is expected to be g_inputWidth x g_scanlineCount in size.
// This sampler should be set up with linear filtering, and either clamp or border addressing.
DECLARE_TEXTURE2D(g_sourceTexture, g_sourceSampler);

// This is the scanline phases texture, generated by GeneratePhaseTexture. It is g_scanlineCount x 1 in size, and each
//  texel component in it represents the phase offset of the NTSC colorburst for the corresponding scanline, in
//  multiples of the colorburst wavelength.
// This sampler should be set up with linear filtering, and either clamp or border addressing.
DECLARE_TEXTURE2D(g_scanlinePhases, g_scanlinePhasesSampler);


CBUFFER consts
{
  // The number of texels that the output texture will contain for each color cycle wave (i.e. the wavelength in output
  //  samples of the color carrier wave).
  uint g_outputTexelsPerColorburstCycle;

  // The width of the input texture.
  uint g_inputWidth;

  // The width of the output render target.
  uint g_outputWidth;

  // The number of scanlines in the current field of video (the height of the input texture).
  uint g_scanlineCount;

  // This is whether we're blending the generated luma/chroma into a single output channel or not. It is expected to
  //  be 0 or 1 (no intermediate values), where "0" means "keep luma and chroma separate, like an S-Video signal" and
  //  "1" means "add the two together, like a composite signal".
  float g_compositeBlend;

  // The scale of any picture instability (horizontal scanline-by-scanline tracking issues). This is used to offset our
  //  texture sampling when generating the output so the picture tracking is imperfect.  Must match the similarly-named
  //  value in GeneratePhaseTexture.
  float g_instabilityScale;

  // A seed for the noise used to generate the scanline-by-scanline picture instability. Must match the simiarly-named
  //  value in GeneratePhaseTexture.
  uint g_noiseSeed;

  // the number of output texels to pad on either side of the signal texture (so that filtering won't have visible
  //  artifacts on the left and right sides).
  uint g_sidePaddingTexelCount;
};


CONST float pi = 3.141592653;


float4 Main(float2 signalTexCoord)
{
  uint2 signalTexelIndex = uint2(floor(signalTexCoord * float2(g_outputWidth, g_scanlineCount)));

  // The texcoord we're using to sample the input texture neesd to be adjusted slightly. The 0.25 offset ensures that
  //  our generated texture is centered on the RGB texture.
  float2 texCoord =
    (float2(signalTexelIndex) * float2(float(g_inputWidth) / float(g_outputWidth), 1)
      + float2(0.25, 0.5))
    / float2(g_inputWidth, g_scanlineCount);

  // Expand our sampling a little bit to adjust for the padding we want on the sides.
  uint effectiveOutputWidth = g_outputWidth - g_sidePaddingTexelCount;
  texCoord.x = (texCoord.x - 0.5) * float(g_outputWidth) / float(effectiveOutputWidth) + 0.5;

  // Add in the instability to our x coordinate to wiggle our generated texture around.
  float instability = CalculateTrackingInstabilityOffset(
    signalTexelIndex.y,
    g_noiseSeed,
    g_instabilityScale,
    g_outputWidth);
  texCoord.x += instability;

  float3 rgb = SAMPLE_TEXTURE(g_sourceTexture, g_sourceSampler, texCoord).rgb;

  // Convert RGB to YIQ using the NTSC standard (SMPTE C) conversion (from https://en.wikipedia.org/wiki/YIQ)
  float3 yiq;

  yiq.r = dot(rgb, float3(0.3000,  0.5900,  0.1100));
  yiq.g = dot(rgb, float3(0.5990, -0.2773, -0.3217));
  yiq.b = dot(rgb, float3(0.2130, -0.5251,  0.3121));

  // Do some gamma adjustments to counter for the gamma that will be used at decode time.
  yiq.x = pow(saturate(yiq.x), 2.2 / 2.0);
  float iqSat = saturate(length(yiq.yz));
  yiq.yz *= pow(iqSat, 2.2 / 2.0) / max(0.00001, iqSat);

  // Separate the YIQ channels out because YIQ.y ended up being confusing (is it Y? no it's I! but also it's y!)
  float Y = yiq.x;
  float I = yiq.y;
  float Q = yiq.z;

  // Calculate the phase for our current x position on the current scanline.
  float2 scanlinePhase = SAMPLE_TEXTURE(
    g_scanlinePhases,
    g_scanlinePhasesSampler,
    (float2(0.0, signalTexelIndex.y + 0.5) / g_scanlineCount)).xy;
  float2 phase = scanlinePhase + signalTexelIndex.x / float(g_outputTexelsPerColorburstCycle);

  // Now we need to encode our IQ component in the carrier wave at the correct phase. This is QAM modulation.
  float2 s, c;
  sincos(2.0 * pi * phase, s, c);

  float2 luma = float2(Y, Y);
  float2 chroma = s * I - c * Q;

  if (g_compositeBlend > 0)
  {
    // We are outputting a composite signal so combine luma and chroma and output it into our expected 1- or 2-channel
    //  texture.
    return (luma + chroma).xyxy;
  }
  else
  {
    // Outputting svideo, so don't combine luma and chroma, and write them out into our expected 2- or 4-channel
    //  texture.
    return float4(luma, chroma).xzyw;
  }
}

PS_MAIN